//单例  懒汉模式
#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<queue>
#include"Log.hpp"
#include"Thread.hpp"
#include"Cond.hpp"
#include"Mutex.hpp"

//对于单例，构造函数要有，但是要私有化

namespace ThreadPoolModule
{       
    using namespace ThreadModule;
    using namespace LogModule;
    using namespace CondModule;
    using namespace MutexModule;

    static const int gnum = 5;     //默认设置5个线程
    template<typename T>
    class ThreadPool
    {
    private:
        void WakeUpAllThread()     //唤醒所有线程
        {
            LockGuard lockguard(_mutex);     //保证安全  加锁
            if(_sleepernum)
                _cond.Broadcast();
            LOG(LogLevel::INFO)<<"唤醒所有的休眠线程";
        }

    //唤醒一个线程
        void WakeUpOne()
        {
            _cond.Signal();
            LOG(LogLevel::INFO)<<"唤醒一个休眠线程";
        }

    //构造函数  设置为私有   单例
        ThreadPool(int num = gnum):_num(num),_isrunning(false),_sleepernum(0)
        {
            for(int i = 0;i<num;i++)
            {
                _threads.emplace_back(
                    [this](){         //把当前对象绑定进去，后调用HandlerTask();
                        HandlerTask();
                    });
            }
        }


        void Start()
        {
            if(_isrunning)   return;    //如果线程池本就是跑起来的，直接返回
                _isrunning = true;
            for(auto &thread : _threads)
            {
                thread.Start();      
        //所有线程都启动，启动就会自动执行回调方法，转到Start，然后转到Routine，而后回调，执行_func,然后执行ThreadPool的
        //[](){}   表达式   然后执行HandlerTask  就会打出来日志信息     
                LOG(LogLevel::INFO)<<"start new thread success: "<<thread.Name();
            }
        }

    //拷贝构造（去掉）
        ThreadPool(const ThreadPool<T> &) = delete;    
    //赋值重载（去掉）
        ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;




    public:
    //创建单例
    //返回 ThreadPool<T> 的接口
    //加上static，才能不需要对象直接使用，static属于类，static之后就可以访问类内static静态属性,不能访问静态成员
        static ThreadPool<T> *GetInstance()   //必须用static修饰，不然需要对象才能被调用，但是构建对象要调用方法（冲突）
        {   
        //防止效率过低，获取单例有锁的情况还需要等待，那就加一层判断(双层if判断，提高效率)
            //第一层判断：拦着所有线程，对象若已经存在就直接返回（不再需要加锁）
            //第一层判断没有拦到，还有加锁，加锁会让线程串行化运行，只有第一个创建成功获取单例，来晚就只能返回
            if(inc == nullptr)
            {
            //进行加锁，保证原子，保证单例
                LockGuard lockguard(_lock);     //这个锁只能是静态static锁
                LOG(LogLevel::DEBUG)<<"获取单例......";
                if(inc == nullptr)      //首次调用创建对象
                {
            //日志
                    LOG(LogLevel::DEBUG)<< "首次使用单例，创建它......";
                    inc = new ThreadPool<T>();     //如果为nullptr  就创建一个ThreadPool对象，并赋给inc
                    inc -> Start();          //构建出来就Start
                }
            }
            return inc;             //后续调用就直接返回，为空不再成立
        }

    //线程池停止操作（在stop之后要全部唤醒）
        void Stop()
        {
            if(!_isrunning)
                return;        //停止了就返回，设置_isrunning为false
            _isrunning = false;

            //唤醒所有的线程
            WakeUpAllThread();
        }

    //等待
        void Join()
        {
            for(auto &thread : _threads)
            {
                thread.Join();
            }
        }


        void HandlerTask()
        {
            char name[128];
            pthread_getname_np(pthread_self(),name,sizeof(name));
            while(true)
            {   
                T t;      //构建任务对象

////////////////////////////////////  对应的就是消费者从队列中取任务  ////////////////////////////////////////             
                {  
    /*
    把下面的判断条件给修改一下，就可以实现
        当线程池退出，_isrunning == false;    
        内部的线程状态：
            1.线程可能正在等待
            2.线程可能等待唤醒
            3.线程可能正在处理任务
        
        线程池在退出的时候，内部的任务，应该被完全取完，并且线程池整体状态 _isrunning(为false)，才能够让对应的线程退出
        不能退出：队列中还有任务 || _isrunning(true)
    */
                    //原子保护，加锁，保证原子性的获取任务
                    LockGuard lockguard(_mutex);
            //1、这里的判断在这就不止是判断任务队列是否为空了
                //a.判断是否队列为空     b.线程池没有退出（队列为空，但是线程池没有退出，才会进行休眠）
                //判断当前队列是否有任务（队列是否为空）   并且线程池没退出，正常运行
                    while(_taskq.empty() && _isrunning)
                    {
                        
                        _sleepernum++;         //休眠一个  休眠计数++
                        _cond.Wait(_mutex);    //没有任务就休眠
                        _sleepernum--;         //休眠结束 就--
                    }

            //2、内部的线程被唤醒（_isrunning为false就一定会走到这里）
                    if(!_isrunning && _taskq.empty())
                    {
                        LOG(LogLevel::INFO)<< name <<"退出了！线程池退出 && 任务队列为空！";
                        break;
                    }

                    //代码走到这一定有任务了    有任务就执行任务
                    t = _taskq.front();     //从q中获取任务，任务已经是线程私有的了
                    _taskq.pop();
                }
                    t();      //处理任务（体现的是消费者可以处理任务）   统一用  t();  方法处理任务
        //问题：处理线程，处理任务，一定要在临界区内部处理吗？
        //不需要！一旦从q队列中获取任务，任务已经是线程私有的了！由当前线程在自己的代码当中进行统一处理！
        //这样就可以一个线程获取结束正在处理任务，他并不持有锁了，另外其他线程仍可以取任务同时处理，这样处理任务就是并行过程
                   
////////////////////////////////////  对应的就是消费者从队列中取任务  ////////////////////////////////////////             
            }
    }

        
    //入任务（向队列中）
        bool Enqueue(const T &in)
        {
            //注意：如果线程池是运行的，才入队列(否则不需要入队列)
            if(_isrunning)
            {
                //方法：先加锁，向任务队列入队，当线程个数等于休眠个数，就要叫醒一个！
                LockGuard lockguard(_mutex);
                _taskq.push(in);
                if(_threads.size() == _sleepernum)     //大于0，表示还有线程在工作；等于0，表示没有一个线程在工作
                    WakeUpOne();
                return true;
            }
            return false;
        }


        ~ThreadPool()
        {}

    private:
        std::vector<Thread> _threads;
        int _num;     //线程池中，线程的个数
        std::queue<T> _taskq;    //任务队列
        Cond _cond;     //这把锁是类内的，类内成员；   静态成员无法直接访问类内的属性
        Mutex _mutex;
        bool _isrunning;         //加一个停止
        int  _sleepernum;        //多少个休眠的


        static ThreadPool<T> *inc;     //单例指针
        static Mutex _lock; //解决单例问题（多线程调用获取单例，会创建多个对象，那也就不是单例了）  锁也当时static的
    };  //重新定义一把静态的锁

        //类内有个静态成员，只能在类外初始化(在类外做了初始化)
        template<typename T>
        ThreadPool<T> *ThreadPool<T>::inc = nullptr;

        //初始化锁
        template<typename T>
        Mutex ThreadPool<T>::_lock;

}


